From 868ab86e7666eea619c5371fe2b32f88d442c9a1 Mon Sep 17 00:00:00 2001 From: Ryan Petrello Date: Tue, 6 Jan 2015 11:16:45 -0500 Subject: Improve ImportError verbosity for configuration files. Fixes bug: 1408008 Change-Id: Iac73b6b0017794de9dea4180d043c18d3fb6d942 --- pecan/configuration.py | 23 ++++++++++++++++++++++- pecan/tests/test_conf.py | 16 ++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/pecan/configuration.py b/pecan/configuration.py index b4109a8..42a9b50 100644 --- a/pecan/configuration.py +++ b/pecan/configuration.py @@ -4,6 +4,11 @@ import os import six +if six.PY3: + from importlib.machinery import SourceFileLoader +else: + import imp + IDENTIFIER = re.compile(r'[a-z_](\w)*$', re.IGNORECASE) @@ -152,8 +157,24 @@ def conf_from_file(filepath): if not os.path.isfile(abspath): raise RuntimeError('`%s` is not a file.' % abspath) + # First, make sure the code will actually compile (and has no SyntaxErrors) with open(abspath, 'rb') as f: - exec(compile(f.read(), abspath, 'exec'), globals(), conf_dict) + compiled = compile(f.read(), abspath, 'exec') + + # Next, attempt to actually import the file as a module. + # This provides more verbose import-related error reporting than exec() + absname, _ = os.path.splitext(abspath) + basepath, module_name = absname.rsplit(os.sep, 1) + if six.PY3: + SourceFileLoader(module_name, abspath).load_module(module_name) + else: + imp.load_module( + module_name, + *imp.find_module(module_name, [basepath]) + ) + + # If we were able to import as a module, actually exec the compiled code + exec(compiled, globals(), conf_dict) conf_dict['__file__'] = abspath return conf_from_dict(conf_dict) diff --git a/pecan/tests/test_conf.py b/pecan/tests/test_conf.py index c273b6f..e682885 100644 --- a/pecan/tests/test_conf.py +++ b/pecan/tests/test_conf.py @@ -144,6 +144,22 @@ class TestConf(PecanTestCase): f.name ) + def test_config_with_non_package_relative_import(self): + from pecan import configuration + with tempfile.NamedTemporaryFile('wb', suffix='.py') as f: + f.write(b_('\n'.join(['from . import variables']))) + f.flush() + configuration.Config({}) + + try: + configuration.conf_from_file(f.name) + except (ValueError, SystemError) as e: + assert 'relative import' in str(e) + else: + raise AssertionError( + "A relative import-related error should have been raised" + ) + def test_config_with_bad_import(self): from pecan import configuration path = ('bad', 'importerror.py') -- cgit v1.2.1