blob: 2d309bee1c69093b6ac3c65359f84851cd13ecb8 [file] [log] [blame]
"""A singleton class for accessing global config values
provides access to global configuration file
"""
__author__ = 'raphtee@google.com (Travis Miller)'
import os, sys, ConfigParser, logging
from autotest_lib.client.common_lib import error
class ConfigError(error.AutotestError):
pass
class ConfigValueError(ConfigError):
pass
common_lib_dir = os.path.dirname(sys.modules[__name__].__file__)
client_dir = os.path.dirname(common_lib_dir)
root_dir = os.path.dirname(client_dir)
# Check if the config files are at autotest's root dir
# This will happen if client is executing inside a full autotest tree, or if
# other entry points are being executed
global_config_path_root = os.path.join(root_dir, 'global_config.ini')
shadow_config_path_root = os.path.join(root_dir, 'shadow_config.ini')
config_in_root = os.path.exists(global_config_path_root)
# Check if the config files are at autotest's client dir
# This will happen if a client stand alone execution is happening
global_config_path_client = os.path.join(client_dir, 'global_config.ini')
config_in_client = os.path.exists(global_config_path_client)
if config_in_root:
DEFAULT_CONFIG_FILE = global_config_path_root
if os.path.exists(shadow_config_path_root):
DEFAULT_SHADOW_FILE = shadow_config_path_root
else:
DEFAULT_SHADOW_FILE = None
RUNNING_STAND_ALONE_CLIENT = False
elif config_in_client:
DEFAULT_CONFIG_FILE = global_config_path_client
DEFAULT_SHADOW_FILE = None
RUNNING_STAND_ALONE_CLIENT = True
else:
DEFAULT_CONFIG_FILE = None
DEFAULT_SHADOW_FILE = None
RUNNING_STAND_ALONE_CLIENT = True
class global_config(object):
_NO_DEFAULT_SPECIFIED = object()
config = None
config_file = DEFAULT_CONFIG_FILE
shadow_file = DEFAULT_SHADOW_FILE
running_stand_alone_client = RUNNING_STAND_ALONE_CLIENT
def check_stand_alone_client_run(self):
return self.running_stand_alone_client
def set_config_files(self, config_file=DEFAULT_CONFIG_FILE,
shadow_file=DEFAULT_SHADOW_FILE):
self.config_file = config_file
self.shadow_file = shadow_file
self.config = None
def _handle_no_value(self, section, key, default):
if default is self._NO_DEFAULT_SPECIFIED:
msg = ("Value '%s' not found in section '%s'" %
(key, section))
raise ConfigError(msg)
else:
return default
def get_section_values(self, section):
"""
Return a config parser object containing a single section of the
global configuration, that can be later written to a file object.
@param section: Section we want to turn into a config parser object.
@return: ConfigParser() object containing all the contents of section.
"""
cfgparser = ConfigParser.ConfigParser()
cfgparser.add_section(section)
for option, value in self.config.items(section):
cfgparser.set(section, option, value)
return cfgparser
def get_config_value(self, section, key, type=str,
default=_NO_DEFAULT_SPECIFIED, allow_blank=False):
"""Get a configuration value
@param section: Section the key is in.
@param key: The key to look up.
@param type: The expected type of the returned value.
@param default: A value to return in case the key couldn't be found.
@param allow_blank: If False, an empty string as a value is treated like
there was no value at all. If True, empty strings
will be returned like they were normal values.
@raises ConfigError: If the key could not be found and no default was
specified.
@return: The obtained value or default.
"""
self._ensure_config_parsed()
try:
val = self.config.get(section, key)
except ConfigParser.Error:
return self._handle_no_value(section, key, default)
if not val.strip() and not allow_blank:
return self._handle_no_value(section, key, default)
return self._convert_value(key, section, val, type)
# This order of parameters ensures this can be called similar to the normal
# get_config_value which is mostly called with (section, key, type).
def get_config_value_with_fallback(self, section, key, fallback_key,
type=str, fallback_section=None,
default=_NO_DEFAULT_SPECIFIED, **kwargs):
"""Get a configuration value if it exists, otherwise use fallback.
Tries to obtain a configuration value for a given key. If this value
does not exist, the value looked up under a different key will be
returned.
@param section: Section the key is in.
@param key: The key to look up.
@param fallback_key: The key to use in case the original key wasn't
found.
@param type: data type the value should have.
@param fallback_section: The section the fallback key resides in. In
case none is specified, the the same section as
for the primary key is used.
@param default: Value to return if values could neither be obtained for
the key nor the fallback key.
@param **kwargs: Additional arguments that should be passed to
get_config_value.
@raises ConfigError: If the fallback key doesn't exist and no default
was provided.
@return: The value that was looked up for the key. If that didn't
exist, the value looked up for the fallback key will be
returned. If that also didn't exist, default will be returned.
"""
if fallback_section is None:
fallback_section = section
try:
return self.get_config_value(section, key, type, **kwargs)
except ConfigError:
return self.get_config_value(fallback_section, fallback_key,
type, default=default, **kwargs)
def override_config_value(self, section, key, new_value):
"""
Override a value from the config file with a new value.
"""
self._ensure_config_parsed()
self.config.set(section, key, new_value)
def reset_config_values(self):
"""
Reset all values to those found in the config files (undoes all
overrides).
"""
self.parse_config_file()
def _ensure_config_parsed(self):
if self.config is None:
self.parse_config_file()
def merge_configs(self, shadow_config):
# overwrite whats in config with whats in shadow_config
sections = shadow_config.sections()
for section in sections:
# add the section if need be
if not self.config.has_section(section):
self.config.add_section(section)
# now run through all options and set them
options = shadow_config.options(section)
for option in options:
val = shadow_config.get(section, option)
self.config.set(section, option, val)
def parse_config_file(self):
self.config = ConfigParser.ConfigParser()
if self.config_file and os.path.exists(self.config_file):
self.config.read(self.config_file)
else:
raise ConfigError('%s not found' % (self.config_file))
# now also read the shadow file if there is one
# this will overwrite anything that is found in the
# other config
if self.shadow_file and os.path.exists(self.shadow_file):
shadow_config = ConfigParser.ConfigParser()
shadow_config.read(self.shadow_file)
# now we merge shadow into global
self.merge_configs(shadow_config)
# the values that are pulled from ini
# are strings. But we should attempt to
# convert them to other types if needed.
def _convert_value(self, key, section, value, value_type):
# strip off leading and trailing white space
sval = value.strip()
# if length of string is zero then return None
if len(sval) == 0:
if value_type == str:
return ""
elif value_type == bool:
return False
elif value_type == int:
return 0
elif value_type == float:
return 0.0
elif value_type == list:
return []
else:
return None
if value_type == bool:
if sval.lower() == "false":
return False
else:
return True
if value_type == list:
# Split the string using ',' and return a list
return [val.strip() for val in sval.split(',')]
try:
conv_val = value_type(sval)
return conv_val
except:
msg = ("Could not convert %s value %r in section %s to type %s" %
(key, sval, section, value_type))
raise ConfigValueError(msg)
def get_sections(self):
"""Return a list of sections available."""
self._ensure_config_parsed()
return self.config.sections()
# insure the class is a singleton. Now the symbol global_config
# will point to the one and only one instace of the class
global_config = global_config()