diff options
Diffstat (limited to 'setuptools')
-rw-r--r-- | setuptools/config.py | 88 | ||||
-rw-r--r-- | setuptools/tests/test_config.py | 2 |
2 files changed, 47 insertions, 43 deletions
diff --git a/setuptools/config.py b/setuptools/config.py index 0a2f51e2..cd1b115e 100644 --- a/setuptools/config.py +++ b/setuptools/config.py @@ -10,6 +10,7 @@ from collections import defaultdict from functools import partial from functools import wraps from importlib import import_module +import contextlib from distutils.errors import DistutilsOptionError, DistutilsFileError from setuptools.extern.packaging.version import LegacyVersion, parse @@ -20,6 +21,44 @@ from setuptools.extern.six import string_types, PY3 __metaclass__ = type +class StaticModule: + """ + Attempt to load the module by the name + """ + def __init__(self, name): + spec = importlib.util.find_spec(module_name) + with open(spec.origin) as strm: + src = strm.read() + module = ast.parse(src) + vars(self).update(locals()) + del self.self + + def __getattr__(self, attr): + try: + return next( + ast.literal_eval(statement.value) + for statement in self.module.body + if isinstance(statement, ast.Assign) + for target in statement.targets + if isinstance(target, ast.Name) and target.id == attr + ) + except Exception: + raise AttributeError( + "{self.name} has no attribute {attr}".format(**locals())) + + +@contextlib.contextmanager +def patch_path(path): + """ + Add path to front of sys.path for the duration of the context. + """ + try: + sys.path.insert(0, path) + yield + finally: + sys.path.remove(path) + + def read_configuration( filepath, find_others=False, ignore_option_errors=False): """Read given configuration file and returns options from it as a dict. @@ -346,50 +385,15 @@ class ConfigHandler: # A custom parent directory was specified for all root modules parent_path = os.path.join(os.getcwd(), package_dir['']) - fpath = os.path.join(parent_path, *module_name.split('.')) - if os.path.exists(fpath + '.py'): - fpath += '.py' - elif os.path.isdir(fpath): - fpath = os.path.join(fpath, '__init__.py') - else: - raise DistutilsOptionError('Could not find module ' + module_name) - with open(fpath, 'rb') as fp: - src = fp.read() - found = False - top_level = ast.parse(src) - for statement in top_level.body: - if isinstance(statement, ast.Assign): - for target in statement.targets: - if isinstance(target, ast.Name) \ - and target.id == attr_name: - try: - value = ast.literal_eval(statement.value) - except ValueError: - found = False - else: - found = True - elif isinstance(target, ast.Tuple) \ - and any(isinstance(t, ast.Name) and t.id == attr_name - for t in target.elts): - try: - stmnt_value = ast.literal_eval(statement.value) - except ValueError: - found = False - else: - for t, v in zip(target.elts, stmnt_value): - if isinstance(t, ast.Name) \ - and t.id == attr_name: - value = v - found = True - if not found: - # Fall back to extracting attribute via importing - sys.path.insert(0, parent_path) + with patch_path(parent_path): try: + # attempt to load value statically + return getattr(StaticModule(module_name), attr_name) + except Exception: + # fallback to simple import module = import_module(module_name) - value = getattr(module, attr_name) - finally: - sys.path = sys.path[1:] - return value + + return getattr(module, attr_name) @classmethod def _get_parser_compound(cls, *parse_methods): diff --git a/setuptools/tests/test_config.py b/setuptools/tests/test_config.py index d8347c78..961f8d42 100644 --- a/setuptools/tests/test_config.py +++ b/setuptools/tests/test_config.py @@ -103,7 +103,7 @@ class TestConfigurationReader: 'version = attr: none.VERSION\n' 'keywords = one, two\n' ) - with pytest.raises(DistutilsOptionError): + with pytest.raises(ImportError): read_configuration('%s' % config) config_dict = read_configuration( |