aboutsummaryrefslogtreecommitdiffstats
path: root/setuptools
diff options
context:
space:
mode:
Diffstat (limited to 'setuptools')
-rw-r--r--setuptools/config.py88
-rw-r--r--setuptools/tests/test_config.py2
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(