From e2c6436e3e7c654a8d4196ac378debf185c74fa4 Mon Sep 17 00:00:00 2001 From: Christopher Allan Webber Date: Thu, 16 Jun 2011 08:21:51 -0500 Subject: Configuration file loading via ConfigObj. Uses ConfigObj to open the config file. Also does validation via the config spec, so defaults are provided, strings are interpolated, types are converted. --- mediagoblin/config.py | 71 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 mediagoblin/config.py (limited to 'mediagoblin/config.py') diff --git a/mediagoblin/config.py b/mediagoblin/config.py new file mode 100644 index 00000000..533dbe19 --- /dev/null +++ b/mediagoblin/config.py @@ -0,0 +1,71 @@ +# GNU MediaGoblin -- federated, autonomous media hosting +# Copyright (C) 2011 Free Software Foundation, Inc +# +# This program 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. +# +# This program 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, see . + +import os +import pkg_resources + +from configobj import ConfigObj +from validate import Validator + + +CONFIG_SPEC_PATH = pkg_resources.resource_filename( + 'mediagoblin', 'config_spec.ini') + + +def _setup_defaults(config, config_path): + """ + Setup DEFAULTS in a config object from an (absolute) config_path. + """ + config.setdefault('DEFAULT', {}) + config['DEFAULT']['here'] = os.path.dirname(config_path) + config['DEFAULT']['__file__'] = config_path + + +def read_mediagoblin_config(config_path, config_spec=CONFIG_SPEC_PATH): + """ + Read a config object from config_path. + + Does automatic value transformation based on the config_spec. + Also provides %(__file__)s and %(here)s values of this file and + its directory respectively similar to paste deploy. + + Args: + - config_path: path to the config file + - config_spec: config file that provides defaults and value types + for validation / conversion. Defaults to mediagoblin/config_spec.ini + + Returns: + A read ConfigObj object. + """ + config_path = os.path.abspath(config_path) + + config_spec = ConfigObj( + CONFIG_SPEC_PATH, + encoding='UTF8', list_values=False, _inspec=True) + + _setup_defaults(config_spec, config_path) + + conf = ConfigObj( + config_path, + configspec=config_spec, + interpolation='ConfigParser') + + _setup_defaults(conf, config_path) + + conf.validate(Validator()) + + return conf + -- cgit v1.2.3 From 4fd487f72e26dfc8390756e4cfea367fe470558b Mon Sep 17 00:00:00 2001 From: Christopher Allan Webber Date: Sat, 18 Jun 2011 15:01:32 -0500 Subject: Validation error reporting functionality. Changed a few things so we can report errors to users properly in the config loading system. - We now return from read_mediagoblin_config both a loaded config and the validation results - We now have a helper function generate_validation_report that can generate a proper validation report saying if there are errors in a way that's useful to users. - Moved conf->config in the read_mediagoblin_config function, which looks nicer IMO. --- mediagoblin/config.py | 63 ++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 57 insertions(+), 6 deletions(-) (limited to 'mediagoblin/config.py') diff --git a/mediagoblin/config.py b/mediagoblin/config.py index 533dbe19..2e457e44 100644 --- a/mediagoblin/config.py +++ b/mediagoblin/config.py @@ -17,7 +17,7 @@ import os import pkg_resources -from configobj import ConfigObj +from configobj import ConfigObj, flatten_errors from validate import Validator @@ -42,13 +42,18 @@ def read_mediagoblin_config(config_path, config_spec=CONFIG_SPEC_PATH): Also provides %(__file__)s and %(here)s values of this file and its directory respectively similar to paste deploy. + This function doesn't itself raise any exceptions if validation + fails, you'll have to do something + Args: - config_path: path to the config file - config_spec: config file that provides defaults and value types for validation / conversion. Defaults to mediagoblin/config_spec.ini Returns: - A read ConfigObj object. + A tuple like: (config, validation_result) + ... where 'conf' is the parsed config object and 'validation_result' + is the information from the validation process. """ config_path = os.path.abspath(config_path) @@ -58,14 +63,60 @@ def read_mediagoblin_config(config_path, config_spec=CONFIG_SPEC_PATH): _setup_defaults(config_spec, config_path) - conf = ConfigObj( + config = ConfigObj( config_path, configspec=config_spec, interpolation='ConfigParser') - _setup_defaults(conf, config_path) + _setup_defaults(config, config_path) + + # For now the validator just works with the default functions, + # but in the future if we want to add additional validation/configuration + # functions we'd add them to validator.functions here. + # + # See also: + # http://www.voidspace.org.uk/python/validate.html#adding-functions + validator = Validator() + validation_result = config.validate(validator, preserve_errors=True) + + return config, validation_result + + +REPORT_HEADER = """\ +There were validation problems loading this config file: +-------------------------------------------------------- +""" + + +def generate_validation_report(config, validation_result): + """ + Generate a report if necessary of problems while validating. + + Returns: + Either a string describing for a user the problems validating + this config or None if there are no problems. + """ + report = [] + + # Organize the report + for entry in flatten_errors(config, validation_result): + # each entry is a tuple + section_list, key, error = entry + + if key is not None: + section_list.append(key) + else: + section_list.append(u'[missing section]') + + section_string = u':'.join(section_list) - conf.validate(Validator()) + if error == False: + # We don't care about missing values for now. + continue - return conf + report.append(u"%s = %s" % (section_string, error)) + if report: + return REPORT_HEADER + u"\n".join(report) + else: + return None -- cgit v1.2.3 From 9dba44de84561c72831dad179db30e4dfb3923fd Mon Sep 17 00:00:00 2001 From: Christopher Allan Webber Date: Sat, 18 Jun 2011 15:18:25 -0500 Subject: Make REPORT_HEADER a unicode string also. Unicode everywhere, ideally! --- mediagoblin/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'mediagoblin/config.py') diff --git a/mediagoblin/config.py b/mediagoblin/config.py index 2e457e44..4f6d9f2e 100644 --- a/mediagoblin/config.py +++ b/mediagoblin/config.py @@ -82,7 +82,7 @@ def read_mediagoblin_config(config_path, config_spec=CONFIG_SPEC_PATH): return config, validation_result -REPORT_HEADER = """\ +REPORT_HEADER = u"""\ There were validation problems loading this config file: -------------------------------------------------------- """ -- cgit v1.2.3 From e5aa1ec6b4507fc1eb9b11428dad824b4285522d Mon Sep 17 00:00:00 2001 From: Christopher Allan Webber Date: Sat, 18 Jun 2011 16:51:35 -0500 Subject: CONFIG_SPEC_PATH should be config_spec here, fixing. --- mediagoblin/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'mediagoblin/config.py') diff --git a/mediagoblin/config.py b/mediagoblin/config.py index 4f6d9f2e..2f93d32c 100644 --- a/mediagoblin/config.py +++ b/mediagoblin/config.py @@ -58,7 +58,7 @@ def read_mediagoblin_config(config_path, config_spec=CONFIG_SPEC_PATH): config_path = os.path.abspath(config_path) config_spec = ConfigObj( - CONFIG_SPEC_PATH, + config_spec, encoding='UTF8', list_values=False, _inspec=True) _setup_defaults(config_spec, config_path) -- cgit v1.2.3