2014-12-07 03:35:20 +01:00
|
|
|
from collections import OrderedDict, namedtuple
|
2014-12-02 07:14:35 +01:00
|
|
|
|
|
|
|
try:
|
|
|
|
# Python 2
|
|
|
|
from ConfigParser import ConfigParser
|
|
|
|
except ImportError:
|
|
|
|
# Python 3
|
|
|
|
from configparser import ConfigParser
|
2014-10-31 06:34:03 +01:00
|
|
|
|
|
|
|
|
2014-12-07 03:35:20 +01:00
|
|
|
Section_format = namedtuple('Section_format', ('name', 'options'))
|
|
|
|
Config_option = namedtuple('Config_option', ('name', 'value_type', 'required'))
|
2014-10-31 06:34:03 +01:00
|
|
|
|
|
|
|
|
2014-12-07 03:35:20 +01:00
|
|
|
def option(name, value_type=str, required=True):
|
|
|
|
'''
|
|
|
|
Given a config file option name, an expected type for its value, and whether it's required,
|
|
|
|
return a Config_option capturing that information.
|
|
|
|
'''
|
|
|
|
return Config_option(name, value_type, required)
|
2014-10-31 06:34:03 +01:00
|
|
|
|
|
|
|
|
2014-12-07 03:35:20 +01:00
|
|
|
CONFIG_FORMAT = (
|
|
|
|
Section_format(
|
|
|
|
'location',
|
|
|
|
(
|
|
|
|
option('source_directories'),
|
|
|
|
option('repository'),
|
|
|
|
),
|
|
|
|
),
|
|
|
|
Section_format(
|
|
|
|
'retention',
|
|
|
|
(
|
|
|
|
option('keep_within', required=False),
|
|
|
|
option('keep_hourly', int, required=False),
|
|
|
|
option('keep_daily', int, required=False),
|
|
|
|
option('keep_weekly', int, required=False),
|
|
|
|
option('keep_monthly', int, required=False),
|
|
|
|
option('keep_yearly', int, required=False),
|
|
|
|
option('prefix', required=False),
|
|
|
|
),
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def validate_configuration_format(parser, config_format):
|
2014-10-31 06:34:03 +01:00
|
|
|
'''
|
2014-12-07 03:35:20 +01:00
|
|
|
Given an open ConfigParser and an expected config file format, validate that the parsed
|
|
|
|
configuration file has the expected sections, that any required options are present in those
|
|
|
|
sections, and that there aren't any unexpected options.
|
2014-12-02 07:35:25 +01:00
|
|
|
|
2014-12-07 03:35:20 +01:00
|
|
|
Raise ValueError if anything is awry.
|
2014-10-31 06:34:03 +01:00
|
|
|
'''
|
|
|
|
section_names = parser.sections()
|
2014-12-07 03:35:20 +01:00
|
|
|
required_section_names = tuple(section.name for section in config_format)
|
2014-10-31 06:34:03 +01:00
|
|
|
|
2014-12-07 03:35:20 +01:00
|
|
|
if set(section_names) != set(required_section_names):
|
2014-10-31 06:34:03 +01:00
|
|
|
raise ValueError(
|
|
|
|
'Expected config sections {} but found sections: {}'.format(
|
2014-12-07 03:35:20 +01:00
|
|
|
', '.join(required_section_names),
|
2014-10-31 06:34:03 +01:00
|
|
|
', '.join(section_names)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
2014-12-07 03:35:20 +01:00
|
|
|
for section_format in config_format:
|
|
|
|
option_names = parser.options(section_format.name)
|
|
|
|
expected_options = section_format.options
|
2014-10-31 06:34:03 +01:00
|
|
|
|
2014-12-07 03:35:20 +01:00
|
|
|
unexpected_option_names = set(option_names) - set(option.name for option in expected_options)
|
|
|
|
|
|
|
|
if unexpected_option_names:
|
|
|
|
raise ValueError(
|
|
|
|
'Unexpected options found in config section {}: {}'.format(
|
|
|
|
section_format.name,
|
|
|
|
', '.join(sorted(unexpected_option_names)),
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
missing_option_names = tuple(
|
|
|
|
option.name for option in expected_options if option.required
|
|
|
|
if option.name not in option_names
|
|
|
|
)
|
|
|
|
|
|
|
|
if missing_option_names:
|
2014-10-31 06:34:03 +01:00
|
|
|
raise ValueError(
|
2014-12-07 03:35:20 +01:00
|
|
|
'Required options missing from config section {}: {}'.format(
|
|
|
|
section_format.name,
|
|
|
|
', '.join(missing_option_names)
|
2014-10-31 06:34:03 +01:00
|
|
|
)
|
|
|
|
)
|
|
|
|
|
2014-12-07 03:35:20 +01:00
|
|
|
|
|
|
|
def parse_section_options(parser, section_format):
|
|
|
|
'''
|
|
|
|
Given an open ConfigParser and an expected section format, return the option values from that
|
|
|
|
section as a dict mapping from option name to value. Omit those options that are not present in
|
|
|
|
the parsed options.
|
|
|
|
|
|
|
|
Raise ValueError if any option values cannot be coerced to the expected Python data type.
|
|
|
|
'''
|
|
|
|
type_getter = {
|
|
|
|
str: parser.get,
|
|
|
|
int: parser.getint,
|
|
|
|
}
|
|
|
|
|
|
|
|
return OrderedDict(
|
|
|
|
(option.name, type_getter[option.value_type](section_format.name, option.name))
|
|
|
|
for option in section_format.options
|
|
|
|
if parser.has_option(section_format.name, option.name)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def parse_configuration(config_filename):
|
|
|
|
'''
|
|
|
|
Given a config filename of the expected format, return the parsed configuration as a tuple of
|
|
|
|
(location config, retention config) where each config is a dict of that section's options.
|
|
|
|
|
|
|
|
Raise IOError if the file cannot be read, or ValueError if the format is not as expected.
|
|
|
|
'''
|
|
|
|
parser = ConfigParser()
|
|
|
|
parser.readfp(open(config_filename))
|
|
|
|
|
|
|
|
validate_configuration_format(parser, CONFIG_FORMAT)
|
|
|
|
|
|
|
|
return tuple(
|
|
|
|
parse_section_options(parser, section_format)
|
|
|
|
for section_format in CONFIG_FORMAT
|
2014-10-31 06:34:03 +01:00
|
|
|
)
|