123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128 |
- from collections import OrderedDict, namedtuple
- try:
- # Python 2
- from ConfigParser import ConfigParser
- except ImportError:
- # Python 3
- from configparser import ConfigParser
- Section_format = namedtuple('Section_format', ('name', 'options'))
- Config_option = namedtuple('Config_option', ('name', 'value_type', 'required'))
- 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)
- 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):
- '''
- 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.
- Raise ValueError if anything is awry.
- '''
- section_names = parser.sections()
- required_section_names = tuple(section.name for section in config_format)
- if set(section_names) != set(required_section_names):
- raise ValueError(
- 'Expected config sections {} but found sections: {}'.format(
- ', '.join(required_section_names),
- ', '.join(section_names)
- )
- )
- for section_format in config_format:
- option_names = parser.options(section_format.name)
- expected_options = section_format.options
- 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:
- raise ValueError(
- 'Required options missing from config section {}: {}'.format(
- section_format.name,
- ', '.join(missing_option_names)
- )
- )
- 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
- )
|