Commit 412d2e17 authored by Jason R. Coombs's avatar Jason R. Coombs

Merge commit 'd6bcf5e8' into feature/2093-docs-revamp

parents 0342c9fd d6bcf5e8
``attr:`` now extracts variables through rudimentary examination of the AST,
thereby supporting modules with third-party imports. If examining the AST
fails to find the variable, ``attr:`` falls back to the old behavior of
importing the module.
......@@ -92,7 +92,7 @@ Metadata and options are set in the config sections of the same name.
* In some cases, complex values can be provided in dedicated subsections for
clarity.
* Some keys allow ``file:``, ``attr:``, and ``find:`` and ``find_namespace:`` directives in
* Some keys allow ``file:``, ``attr:``, ``find:``, and ``find_namespace:`` directives in
order to cover common usecases.
* Unknown keys are ignored.
......@@ -146,6 +146,12 @@ Special directives:
* ``attr:`` - Value is read from a module attribute. ``attr:`` supports
callables and iterables; unsupported types are cast using ``str()``.
In order to support the common case of a literal value assigned to a variable
in a module containing (directly or indirectly) third-party imports,
``attr:`` first tries to read the value from the module by examining the
module's AST. If that fails, ``attr:`` falls back to importing the module.
* ``file:`` - Value is read from a list of files and then concatenated
......@@ -237,4 +243,4 @@ data_files dict 40.6.0
Notes:
1. In the `package_data` section, a key named with a single asterisk (`*`)
refers to all packages, in lieu of the empty string used in `setup.py`.
\ No newline at end of file
refers to all packages, in lieu of the empty string used in `setup.py`.
from __future__ import absolute_import, unicode_literals
import ast
import io
import os
import sys
......@@ -344,14 +345,50 @@ class ConfigHandler:
elif '' in package_dir:
# A custom parent directory was specified for all root modules
parent_path = os.path.join(os.getcwd(), package_dir[''])
sys.path.insert(0, parent_path)
try:
module = import_module(module_name)
value = getattr(module, attr_name)
finally:
sys.path = sys.path[1:]
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)
try:
module = import_module(module_name)
value = getattr(module, attr_name)
finally:
sys.path = sys.path[1:]
return value
@classmethod
......
......@@ -103,7 +103,7 @@ class TestConfigurationReader:
'version = attr: none.VERSION\n'
'keywords = one, two\n'
)
with pytest.raises(ImportError):
with pytest.raises(DistutilsOptionError):
read_configuration('%s' % config)
config_dict = read_configuration(
......@@ -300,6 +300,18 @@ class TestMetadata:
with get_dist(tmpdir) as dist:
assert dist.metadata.version == '2016.11.26'
subpack.join('submodule.py').write(
'import third_party_module\n'
'VERSION = (2016, 11, 26)'
)
config.write(
'[metadata]\n'
'version = attr: fake_package.subpackage.submodule.VERSION\n'
)
with get_dist(tmpdir) as dist:
assert dist.metadata.version == '2016.11.26'
def test_version_file(self, tmpdir):
_, config = fake_env(
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment